(function(){

    // basic settings;
    var bgColorWhite       = '#ffffff';
    var bgColorNormalDay   = '#ffffee';
    var bgColorToday       = '#0cd001';
    var bgColorSunday      = '#ff90cc';
    var bgColorSaturday    = '#3ec0ee';
    var bgColorHoliday     = '#ff90cc';
    var bgColorCalFloat    = '#ffa';
    
    var colorListNormal    = '#000';
    var bgColorListNormal  = '#fff';
    var bgColorListFloat   = '#bfb';
    
    var bgColorSelfEnable  = '#fff';
    var bgColorSelfDisable = '#ddd';
    var colorMainMonth     = '#000';
    var colorSubMonth      = '#999';

    var diaryTitlePrefix   = '%A1%DA%A4%D2%A4%DE%A5%B7%A5%A7%A5%A2%A1%DB';
    var diaryTitleSuffix   = '%A4%CB%A4%D2%A4%DE%A4%CA%A4%DF%A4%F3%A4%CA%A4%D8'; // 'にひまなみんなへ'
    var diaryBodyTop       = '%A4%CB%B8%C2%C4%EA%A4%B7%A4%C6%B8%F8%B3%AB%C3%E6%A1%AA%0D%0A%0D%0A'; // '[member]に限定して公開中！\n\n'
    var diaryBodyMiddle    = '%A4%D2%A4%DE%A4%CA%A4%E9%BD%B8%A4%DE%A4%C3%A4%C6%CD%B7%A4%DC%A4%A6%A4%E8%A1%BC%0D%0A%0D%0A%0D%0A'; // '[date]ひまなら集まって遊ぼうよー\n\n\n'
    var diaryBodyBottom    = '%A2%A8%A4%D2%A4%DE%A5%B7%A5%A7%A5%A2%A5%AB%A5%EC%A5%F3%A5%C0%A1%BC%A4%CE%B3%CE%C7%A7%A4%CF%A4%B3%A4%C1%A4%E9%A4%AB%A4%E9%0D%0A%A1%DAPC%B8%C2%C4%EA%A1%DBhttp%3A%2F%2Fmixi.jp%2Frun_appli.pl%3Fid%3D'; // '※ひまシェアカレンダーの確認はこちらから\n【PC限定】[url]'
    var nameSuffix         = '%A4%B5%A4%F3'; // 'さん'
    var nameSeparator      = '%A1%A2'; // '、'
    

    // js variables
    var monthShift     = 0;
    var dateArray      = new Array();
    var colorArray     = new Array(
        '33ccff', 'ff33cc', '00ff00', 'ff9900', '0000ff', '339933', 
        '990099', 'cccc00', 'ff0000', '999999', 'ae4d1d', '000000'
    );
    var colorAssign    = new Array();
    var showingUserIds = new Array();
    var myMixiFullList = new Array();
    var optinUserIds   = new Array();
    var monthCache     = new Array();
    var newInvites     = new Array();
    var inviteeIds     = new Array();
    var loadedTabData  = new Array(''+1);
    var showMySchedule = true;
    var lastClick      = 0;
    var sendMessIndex  = 0;
    var mouseX         = 0;
    var mouseY         = 0;
    var openedTab      = 1;
    var openedWindow;
    var ownerInfo;
    var viewerInfo;
    var invitingDate;
    
    jQuery(function($) {
        $(window).autoHeight();
        makeHtml();
        writeCurrentMonth();
        colorWeekEnds();
        makeDateArray();
        getHolidays();
        colorToday();
        $.parallel([
            $.getData('/people/@owner/@self'  , {}),
            $.getData('/people/@viewer/@self' , {}),
            $.getData('/appdata/@viewer/@self', { fields: 'select_list,check_list' }),
            $.getData('/people/@owner/@friends/@app' , { startIndex: 0, count: 1000 }),
            $.getData('/people/@viewer/@friends/@app', { startIndex: 0, count: 1000 })
        ])
        .next(function(data){
            var owner         = data[0];
            var viewer        = data[1];
            var savedData     = data[2];
            var ownerFriends  = data[3];
            var viewerFriends = data[4];

            ownerInfo         = owner[0];
            viewerInfo        = viewer[0];
            savedData         = savedData[viewerInfo.id] ? savedData[viewerInfo.id] : {};
            var savedList     = savedData.select_list    ? savedData.select_list    : new Array();
            var checkedFlags  = savedData.check_list     ? savedData.check_list     : new Array();

            var viewerFriendIds = new Array();
            $.each(viewerFriends, function(i, member){ viewerFriendIds.push('m'+member.id) });

            showingUserIds = $.grep(savedList, function(val){
                return ($.inArray(val, viewerFriendIds) != -1);
            });
            if (ownerInfo.id != viewerInfo.id) showingUserIds.push('m'+viewerInfo.id);

            $.each(checkedFlags, function(i, isChecked){
                $('.hima_flag_switch').eq(i).attr('checked', isChecked);
            });

            $('.user_thumb', '#my_schedule_switch')
                .attr('src', ownerInfo.thumbnailUrl)
                .attr('alt', ownerInfo.nickname).show();
            $('.display_name', '#my_schedule_switch').text(ownerInfo.nickname);
            $('#my_schedule_switch').click(function(e){
                mouseX = e.pageX;
                mouseY = e.pageY;
                if (ownerInfo.id != viewerInfo.id) return;
                showMySchedule = !showMySchedule;
                $('.show_mine').toggle(showMySchedule);
                if (showMySchedule) {
                    $(this).css('background-color', bgColorSelfEnable);
                    addTips('自分を表示します。');
                }
                else {
                    $(this).css('background-color', bgColorSelfDisable);
                    addTips('自分を表示しません。');
                }
            });
            
            var initialMembers = new Array();
            $.each(ownerFriends, function(i, person) {
                if ($.inArray('m'+person.id, viewerFriendIds) < 0 && person.id != viewerInfo.id) return true;
                myMixiFullList['m'+person.id] = person;
                optinUserIds.push(person.id);
                var tmpl = $('#my_mixi_tmpl').clone();
                $('.user_thumb', tmpl).attr('src', person.thumbnailUrl).attr('alt', person.nickname).show();
                $('.id', tmpl).text(person.id);
                $('.display_name', tmpl).text(person.nickname);
                tmpl.hover(
                    function(){$(this).css('background-color', bgColorListFloat)},
                    function(){$(this).css('background-color', bgColorListNormal)}
                );
                
                if ($.inArray('m'+person.id, showingUserIds) != -1 && $('#selected_my_mixi .id').length < 12) {
                    var color = colorArray.shift();
                    colorAssign['m'+person.id] = color;
                    tmpl.css('color', '#' + color).appendTo('#selected_my_mixi').show();
                    initialMembers.push(person.id);
                }
                else
                    tmpl.css('color', colorListNormal).appendTo('#my_mixi_list').show();
                
                tmpl.click(function(e){
                    mouseX = e.pageX;
                    mouseY = e.pageY;
                    if ($.inArray('m'+person.id, showingUserIds) != -1) {
                        colorArray.unshift(colorAssign['m'+person.id]);
                        $(this).css('color', colorListNormal).css('background-color', bgColorListNormal).appendTo('#my_mixi_list');
                        showingUserIds = $.grep(showingUserIds, function(val){ return (val != 'm'+person.id) });
                        $('.hima_grid').each(function(){
                            if ($('.id', this).text() == person.id)
                                $(this).remove();
                        });
                    }
                    else if ($('#selected_my_mixi .id').length < 12) {
                        var color = colorArray.shift();
                        colorAssign['m'+person.id] = color;
                        $(this).css('color', '#' + color).css('background-color', bgColorListNormal).appendTo('#selected_my_mixi');
                        showingUserIds.push('m'+person.id);
                        getSchedule([person.id]);
                    }
                    else addTips('一度に表示できるのは１２人までです。');
                    $.postData('/appdata/@viewer/@self', { select_list: showingUserIds }, function(){});
                });
            });
            
            initialMembers.push(ownerInfo.id);
            getSchedule(initialMembers);
            getScheduleCount(optinUserIds);
            openTab(1);
            
            // param check
            //var params   = gadgets.views.getParams();
            //var inviteId = params['invite_id'];
            //if (inviteId)getInvite(inviteId);
            //else checkUnseenInvite();
            
            //main process done
        })
        .error(function(e){
            //console.log(''+e);
        });
        
        // event handling
        
        $('.add_switch').each(function(){
            var index = $('.add_switch').index(this);
            $(this).click(function(e){
                mouseX = e.pageX;
                mouseY = e.pageY;
                var baseTime = (new Date).getTime();
                if (ownerInfo.id != viewerInfo.id)    addTips(
                    ownerInfo.nickname + 'さんのカレンダーです。　表示のみです。');
                else if (!showMySchedule)             addTips('自分の表示をオンにしてください。');
                else if (isLoadingAt(index))          addTips('処理中です。');
                else if (baseTime - lastClick < 1000) addTips('少し時間をおいてください。');
                else {
                    lastClick      = baseTime;
                    var myArea     = $('.show_mine').eq(index);
                    var grid       = $('.hima_grid:first', myArea);
                    var scheduleId = $('.schedule_id', grid).text();
                    var flag       = $('.schedule_flag', grid).text();
                    var nextFlag   = getNextHimaStatus(flag);
                    startLoadingAt(index);
                    if (!scheduleId) {
                        if (nextFlag == 3) {
                            addTips('全てのひまが非表示です。');
                            endLoadingAt(index);
                            return;
                        }
                        var date = indexToDate(index);
                        addMySchedule(date, nextFlag);
                    }
                    else {
                        grid.hide();
                        if (nextFlag == 3)
                            removeMySchedule(index, scheduleId);
                        else
                            updateMySchedule(index, scheduleId, nextFlag);
                    }
                }
            }).hover(function(){
                $('.calendar_date_grid').eq(index).css('background-color', bgColorCalFloat);
                var list = new Array();
                if ($('.show_mine').eq(index).children(':visible').length && showMySchedule)
                    list.push($('.show_mine').eq(index).children(':visible').clone());
                if ($('.my_mixi_hima').eq(index).children(':visible').length)
                    list.push($('.my_mixi_hima').eq(index).children(':visible').clone());
                $.each(list, function(index, grid){
                    $('.user_thumb'  , grid).attr('width', '38').attr('height', '38').show();
                    $('.display_name', grid).show();
                    $('.event_title' , grid).hide();
                    $('.hima_icon'   , grid).hide();
                    grid.appendTo('#my_mixi_hima_list');
                });
                $('#my_mixi_hima_list').children(':not(:last)').after('<br />');
                if (list.length) {
                    var offset = $(this).offset();
                    var adjustV = offset.left < 500 ? $(this).width() + 2 : ($('#my_mixi_hima_list').width() + 4) * -1;
                    var adjustH = offset.top  < 400 ? 0 : $(this).height() - $('#my_mixi_hima_list').height() - 2;
                    $('#my_mixi_hima_list').css('top', offset.top + adjustH).css('left', offset.left + adjustV).show();
                }
            }, function(){
                $('.calendar_date_grid').eq(index).css('background-color', bgColorWhite);
                $('#my_mixi_hima_list').empty().hide();
            });
        });
        
        $('.show_contact_menu').click(function(){
            var index = $('.show_contact_menu').index(this);
            var targetDate = indexToDate(index);
            var friends = $('.my_mixi_hima').eq(index).children(':visible');
            var idList   = new Array();
            var month = targetDate.substring(5,  7) - 0;
            var date  = targetDate.substring(8, 10) - 0;
            invitingDate = targetDate;
            $('.contact_target_date').text(month+'月'+date+'日');
            if (friends.length > 0) {
                friends.each(function(i, friend){
                    var memberId = $('.id', friend).text();
                    var member = myMixiFullList['m'+memberId];
                    var tmpl = $('#target_my_mixi_tmpl').clone();
                    idList.push(memberId);
                    $('.display_name', tmpl).text(member.nickname).show();
                    $('.user_thumb', tmpl).attr('src', member.thumbnailUrl).attr('alt', member.nickname).show();
                    $('.id', tmpl).text(memberId);
                    tmpl.appendTo('.contact_targets').show();
                    tmpl.click(function(){
                        closeSubWindow();
                        invite((new Array(memberId)), targetDate, 'notification', function(result){notifyInvite(result)});
                    }).hover(function(){
                        $(this).css('background-color', bgColorListFloat);
                    },
                    function(){
                        $(this).css('background-color', bgColorListNormal);
                    });
                });
            }
            else
                $('.contact_targets').text('この日はだれもいません。');
            inviteeIds = idList;
            openSubWindow('#contact_confirm');
            sendMessIndex = index;
        });
        
        $('a.add_invite_diary_link').click(function(){
            var url = makeDiaryUrl(inviteeIds, invitingDate);
            $('a.add_invite_diary_link').attr('href', url);
            invite(inviteeIds, invitingDate, 'diary', function(){return});
        });
        
        //$('.open_invite_list').click(function(){
        $('.control').click(function(){
            return;
            if (isRelease) return;
            if (ownerInfo.id != viewerInfo.id) return;
            getInviteList();
        });
        
        // shout
        $('#hima_shout').click(function(e){
            mouseX = e.pageX;
            mouseY = e.pageY;
            addFeed('.oＯ( 暇ですわぁ )', 0,
                function(){ addTips('.oＯ( 暇ですわぁ )') },
                function(){ addTips('更新情報の登録に失敗しました。') }
            );
        });
        
        // my mixi invite
        $('.my_mixi_invite').click(function(){
            if (isRelease) {
                opensocial.requestShareApp(opensocial.IdSpec.PersonId.VIEWER, null, function(response) {
                    if (response.hadError()) { var errCode = response.getErrorCode(); }
                    else { addMessage('マイミクを招待しました。'); }
                    return;
                });
            }
            else addMessage('マイミクを招待しました。');
        });
        
        // schedule flag switch
        $('.hima_flag_switch').click(function(){
            var flag = $('.hima_flag_switch').index(this);
            var isChecked = $(this).attr('checked');
            $('.hima_grid').each(function(){
                if ($('.schedule_flag', this).text() == flag)
                    $(this).toggle(isChecked);
            });
            var saveData = new Array();
            $('.hima_flag_switch').each(function(i){
                saveData.push($(this).attr('checked'));
            });
            $.postData('/appdata/@viewer/@self', { check_list: saveData }, function(){});
        });
        
        $('#next_month')      .click(function(){ monthShift++; refreshCalendar(); });
        $('#prev_month')      .click(function(){ monthShift--; refreshCalendar(); });
        $('#this_month')      .click(function(){ monthShift=0; refreshCalendar(); });
        $('.close_sub_window').click(function(){ closeSubWindow() });
        $('.filter')          .click(function(){ closeSubWindow() });
        $('.tab_selector')    .click(function(){ openTab($('.tab_selector').index(this)) });
    });
    
    // functions
    
    function indexToDate (index) { return dateArray[index]; }
    function dateToIndex (date)  { return $.inArray(date, dateArray); }
    
    function startLoadingAt (index) {
        var loadingImage = $('.loading_image:first').clone();
        $('.show_mine').eq(index).append(loadingImage);
        loadingImage.show();
    }
    
    function endLoadingAt (index) {
        var myArea = $('.show_mine').eq(index);
        $('.loading_image', myArea).remove();
    }
    
    function isLoadingAt (index) {
        var myArea = $('.show_mine').eq(index);
        return $('.loading_image', myArea).length;
    }
    
    function setMonthCache (key, data) {
        if (!monthCache['m'+monthShift])
            monthCache['m'+monthShift] = new Array();
        monthCache['m'+monthShift][key] = data;
    }
    
    function getMonthCache (cacheKey) {
        if (!monthCache['m'+monthShift]) return;
        return monthCache['m'+monthShift][cacheKey];
    }
    
    function deleteMonthCache (key) {
        if (key) monthCache['m'+monthShift][key] = 0;
        else     monthCache['m'+monthShift] = new Array();
    }
    
    function openSubWindow (subWindow) {
        if (openedWindow) closeSubWindow();
        var adjustV = 350 - $(subWindow).width() / 2;
        var adjustH = 300 - $(subWindow).height() / 2;
        if (adjustH < 10) adjustH = 10;
        $(subWindow).css('z-index', '5').css('top', adjustH).css('left', adjustV).show();
        $('.filter').css('z-index', '3').show();
        openedWindow = subWindow;
    }
    
    function closeSubWindow () {
        $(openedWindow).css('z-index', '0').hide();
        $('.filter').css('z-index', '0').hide();
        switch(openedWindow) {
            case '#message_window':
                $('#message_window .alert_area').text('');
                break;
            case '#contact_confirm':
                $('.contact_targets').empty();
                $('.feed_message').val('');
                break;
            case '#invite_list':
                $('#invite_list .invited_list').empty();
                $('#invite_list .inviting_list').empty();
                break;
        }
        openedWindow = '';
    }
    
    function openTab (index) {
        $('.tab_canvas').eq(openedTab).hide();
        if (!loadedTabData[index]) {
            switch (index) {
                case 1:
                    getFootPrint();
                    break;
                default:
                    break;
            }
            loadedTabData[index] = 1;
        }
        $('.tab_canvas').eq(index).show();
        openedTab = index;
    }
    
    function isShowingFlag (flag) {
        var isChecked = $('.hima_flag_switch').eq(flag).attr('checked');
        return isChecked;
    }
    
    function getNextHimaStatus (flag) {
        if (flag == null || flag == undefined || flag == '') flag = -1;
        var nextStatus;
        while (!isShowingFlag(++flag % 4)) {
            if (flag == 3) break;
        }
        return flag;
    }
    
    function getSchedule (memberIdList) {
        if (!memberIdList) return;
        var memberIdStr = memberIdList.join('-');
        var cache       = getMonthCache(memberIdStr);
        if (cache) {
            getScheduleCallback(cache);
            return;
        }
        var apiUrl = apiUrlPrefix + 'schedule_select_list.php?member_id=' + memberIdStr +
            '&start_date=' + dateArray[0] + '&end_date=' + dateArray[41] + '&check=1&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){
                setMonthCache(memberIdStr, data);
                getScheduleCallback(data);
            },
            function(){ addError('スケジュールの取得に失敗しました。') }
        );
    }
    
    function getScheduleCallback (data) {
        if (!data.result) return;
        $.each(data.result, function(i, schedule) {
            if (schedule.member_id == ownerInfo.id)
                addMyScheduleScreen(schedule);
            else
                addMyMixiScheduleScreen(schedule);
        });
    }
    
    function addMySchedule (date, flag) {
        var apiUrl = apiUrlPrefix + 'schedule_insert.php?start_date=' + date +
            '&end_date=' + date + '&flag=' + flag + '&stamp=' + (new Date).getTime();
        extAPI(apiUrl, 
            function(data){
                var schedule = data.result;
                var time = schedule.flag == 0 ? '一日' :
                    (schedule.flag == 1 ? '昼' : '夜');
                deleteMonthCache();
                addMyScheduleScreen(schedule);
                addTips(time+'ひまにしました。');
                notifyAddSchedule();
            },
            function(){
                addTips('ひまの追加に失敗しました');
                endLoadingAt(dateToIndex(date));
            }
        );
    }
    
    function updateMySchedule (index, scheduleId, flag) {
        var apiUrl = apiUrlPrefix + 'schedule_update.php?schedule_id=' + scheduleId +
            '&flag=' + flag + '&stamp=' + (new Date).getTime();
        extAPI(apiUrl, 
            function(data){
                deleteMonthCache();
                if (!data.result) return;
                var schedule = data.result;
                var time = schedule.flag == 1 ? '昼' : '夜'
                addTips(time+'ひまにしました。');
                updateMyScheduleScreen(schedule);
            },
            function(){
                addTips('ひまの変更に失敗しました。');
                endLoadingAt(index);
                var myArea = $('.show_mine').eq(index);
                $('.hima_grid:first', myArea).show();
            }
        );
    }
    
    function removeMySchedule(index, scheduleId) {
        var apiUrl = apiUrlPrefix + 'schedule_delete.php?schedule_id=' + scheduleId +
            '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){
                deleteMonthCache();
                removeMyScheduleScreen(index, scheduleId);
                addTips('ひまを消しました。');
            },
            function(){
                addError('ひまの削除に失敗しました。');
                endLoadingAt(index);
                var myArea = $('.show_mine').eq(index);
                $('.hima_grid:first', myArea).show();
            }
        );
        return;
    }
    
    function getFootPrint () {
        var apiUrl = apiUrlPrefix + 'check_select_info.php?stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){ addFootPrintScreen(data.result) },
            function(){ addError('足あとの取得に失敗しました。') }
        );
        return;
    }
    
    function notifyAddSchedule () {
        $.getData('/appdata/@viewer/@self', { fields: 'last_feed' }, function(data){
            var saved = data[ownerInfo.id];
            var lastFeed = saved && saved.last_feed ? saved.last_feed : 0;
            var now = (new Date).getTime();
            if (lastFeed == 0 || (now - lastFeed) > 3600000) {
                addFeed('ひまな日を登録しました', 0,
                    function(){},
                    function(){ addError('更新情報の登録に失敗しました。') }
                );
                $.postData('/appdata/@viewer/@self', { last_feed: now }, function(){});
            }
        });
    }
    
    function getScheduleCount (memberIdList) {
        if (!memberIdList || memberIdList.length == 0) return;
        // マイミク全員が対象なので後々クエリを分けないと。。
        var memberIdStr = memberIdList.join('-');
        var apiUrl = apiUrlPrefix + 'schedule_select_count.php?member_id=' + memberIdStr +
            '&start_date=' + dateArray[0] + '&end_date=' + dateArray[41] + '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){
                if (data.result) {
                    $('.my_mixi_column').each(function(){
                        var id = $('.id', this).text();
                        if (!id) return true;
                        var count = data.result[id].count;
                        $('.hima_count', this).text('('+count+')');
                    });
                }
            },
            function(){ addError('') }
        );
    }
    
    function addMyScheduleScreen (schedule) {
        var index  = dateToIndex(schedule.start_date);
        var grid   = $('.hima_grid:first').clone();
        var image = apiUrlPrefix + 'static/img/himaji/hima_ffffff_' + schedule.flag + '.gif';
        endLoadingAt(index);
        if (showMySchedule && isShowingFlag(schedule.flag)) grid.show();
        $('.show_mine').eq(index).append(grid);
        $('.hima_icon', grid).attr('src', image).show();
        $('.schedule_id', grid).text(schedule.id);
        $('.id', grid).text(ownerInfo.id);
        $('.user_thumb', grid).attr('src', ownerInfo.thumbnailUrl).hide();
        $('.schedule_flag', grid).text(schedule.flag);
        $('.display_name', grid).text(ownerInfo.nickname).hide();
        //$('.event_title', grid).text('ひま').hide();
        return;
    }
    
    function addMyMixiScheduleScreen (schedule) {
        var index  = dateToIndex(schedule.start_date);
        var myMixi = myMixiFullList['m'+schedule.member_id];
        var color  = colorAssign['m'+schedule.member_id];
        var grid   = $('.hima_grid:first').clone();
        var image  = apiUrlPrefix + 'static/img/himaji/hima_' + color + '_' + schedule.flag + '.gif';
        if (isShowingFlag(schedule.flag)) grid.show();
        $('.my_mixi_hima').eq(index).append(grid);
        $('.hima_icon', grid).attr('src', image).show();
        $('.schedule_id', grid).text(schedule.id);
        $('.id', grid).text(schedule.member_id);
        $('.user_thumb', grid).attr('src', myMixi.thumbnailUrl).hide();
        $('.schedule_flag', grid).text(schedule.flag);
        $('.display_name', grid).text(myMixi.nickname).hide();
        //$('.event_title', grid).text('ひま').hide();
        return;
    }
    
    function updateMyScheduleScreen (schedule) {
        var index  = dateToIndex(schedule.start_date);
        var myArea = $('.show_mine').eq(index);
        var grid   = $('.hima_grid:first', myArea);
        var image = apiUrlPrefix + 'static/img/himaji/hima_ffffff_' + schedule.flag + '.gif';
        endLoadingAt(index);
        grid.show();
        if (!isShowingFlag(schedule.flag)) {
            grid.oneTime(2000, function(){
                $(this).hide();
                addTips('非表示にします。');
            });
        }
        $('.hima_icon', grid).attr('src', image).show();
        $('.schedule_flag', grid).text(schedule.flag);
        //$('.event_title', himaGrid).text('ひま').hide();
        return;
    }
    
    function removeMyScheduleScreen (index, scheduleId) {
        var myArea = $('.show_mine').eq(index);
        $('.hima_grid:first', myArea).remove();
        endLoadingAt(index);
        return;
    }
    
    function addFootPrintScreen (checks) {
        if (ownerInfo.id != viewerInfo.id) {
            $('#footprint_list').remove();
            $('.tab_canvas').eq(1).append(
                ownerInfo.nickname + 'さんのカレンダーを表示中のため<br />足あとは見れません<br />' +
                '自分のカレンダーの確認は<a href="http://mixi.jp/run_appli.pl?id=' + appId +
                '" target="_parent">こちら</a>から'
            );
            return;
        }
        if (!checks) return;
        var count = checks.count;
        var list  = checks.list.reverse();
        $('.footprint_count').text(count);
        $.each(list, function(i, check){
            var footprint = $('.footprint:first').clone().show();
            var member = check.visitor_id == ownerInfo.id
                ? ownerInfo
                : myMixiFullList['m'+check.visitor_id];
            var url = 'http://mixi.jp/run_appli.pl?id=' + appId + '&owner_id=' + check.visitor_id;
            if (!member) return true;
            $('.date', footprint).text(check.date.replace(/\d{4}-(\d{2})-(\d{2})/, function(str, p1, p2){return p1+'月'+p2+'日'}));
            $('.time', footprint).text(check.time.substr(0, 5));
            $('.name', footprint).text(member.nickname);
            $('.name', footprint).attr('href', url);
            footprint.appendTo('#footprint_list');
        });
    }
    
    function invite (idList, targetDate, via, callbackFn) {
        if (!idList || !targetDate || !via) return;
        var memberIdStr = idList.join('-');
        var apiUrl = apiUrlPrefix + 'invite_create.php?member_id=' + memberIdStr +
            '&date=' + targetDate + '&via=' + via + '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){ callbackFn(data.result) },
            function(){ addAlert('お誘いの作成に失敗しました。') }
        );
    }
    
    function notifyInvite (invite) {
        if (!invite) return;
        var params = { invite_id: ''+invite.id };
        var escaped = gadgets.io.encodeValues({
            appParams: gadgets.json.stringify(params)
        });
        var title = '【ひまシェア】メッセージのお知らせ'
        var url   = "http://mixi.jp/run_appli.pl?id="+appId;
        var month = invite.target_date.substring(5,  7) - 0;
        var date  = invite.target_date.substring(8, 10) - 0;
        var body  = '※'+month+'/'+date+"の予定へのメッセージです。\n"+
            "※ひまな日の確認、追加、削除はこちらから\n"+url;
        open_notification(
            invite.members[0].member_id,
            title,
            body,
            function(){
                fixInvite(invite.id);
            }
        );
    }
    
    function fixInvite (inviteId) {
        if (!inviteId) return;
        var apiUrl = apiUrlPrefix + 'invite_fix.php?invite_id=' + inviteId +
            '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){ },
            function(){ addAlert('お誘いの作成に失敗しました。') }
        );
    }
    
    function getInvite (inviteId) {
        var apiUrl = apiUrlPrefix + 'invite_select.php?invite_id=' + inviteId + '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){ showInvite(data.result) },
            function(){ addError('お誘いの詳細取得に失敗しました。') }
        );
    }
    
    function showInvite(invite) {
        if (!invite || !invite.id) return;
        if (isRelease && invite.owner_id == viewerInfo.id) return;
        var inviteOwner = invite.owner_id == ownerInfo.id ? ownerInfo : myMixiFullList['m'+invite.owner_id];
        if (!inviteOwner) return;
        var target_date  = invite.target_date.replace(/-0/g, '-').replace(/-/g, '/');
        var create_stamp = invite.create_stamp.replace(/-0/g, '-').replace(/-/g, '/');
        $('.invite_owner .owner_thumb').attr('src', inviteOwner.thumbnailUrl).attr('alt', inviteOwner.nickname);
        $('.invite_owner .owner_name').text(inviteOwner.nickname);
        $('.invite_message').append(invite.message).append('<br />&nbsp;&nbsp;('+create_stamp+')');
        $('#invite_date').text(target_date);
        var replied  = 0;
        var others   = 0;
        var involved = 0;
        $.each(invite.members, function(i, invitee){
            var inviteeInfo = invitee.member_id == ownerInfo.id ? ownerInfo : myMixiFullList['m'+invitee.member_id];
            if (invitee.member_id == viewerInfo.id) {
                involved++;
                if (invitee.status != 'na') return ++replied;
            }
            if (invitee.status == 'reject') return true;
            if (!inviteeInfo)               return ++others;
            var inviteeTmpl = $('.invitee_area:last').clone();
            $('.invitee_thumb', inviteeTmpl).attr('src', inviteeInfo.thumbnailUrl).attr('alt', inviteeInfo.nickname);
            $('.invitee_name' , inviteeTmpl).text(inviteeInfo.nickname);
            inviteeTmpl.appendTo('.invitees_area').show();
        });
        if (!involved) return;
        if (replied)   return;
        if (others) $('<div>その他マイミク外' + others + '人</div>').addClass('other_invitee').appendTo('.invitees_area');
        var subject = '%A4%D2%A4%DE%A5%B7%A5%A7%A5%A2';
        $('.reply_message').attr('href', 'http://mixi.jp/send_message.pl?id=' + invite.owner_id +
            '&mode=edit&subject=' + subject);
        $('.reject_invite').click(function(){ rejectInvite(invite.id); });
        openSubWindow('.invite_window');
    }
    
    function getInviteList () {
        if (ownerInfo.id != viewerInfo.id) return;
        var apiUrl = apiUrlPrefix + 'invite_select_list.php?stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){ showInviteList(data) },
            function(){ addError('お誘いリスト取得に失敗しました。') }
        );
    }
    
    function checkUnseenInvite () {
        if (ownerInfo.id != viewerInfo.id) return;
        var apiUrl = apiUrlPrefix + 'invite_select_unseen_ids.php?stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(data){
                if (!data.result.length) return;
                newInvites = data.result;
                addNotation('お誘いがきています', function(){
                    var id = newInvites.pop();
                    if (id)
                        getInvite(id);
                    else
                        addNotation('', function(){ });
                });
            },
            function(){ addError('NG') }
        );
    }
    
    function showInviteList (data) {
        if (!data || !data.result) return;
        $.each(data.result, function(i, invite) {
            if (invite.owner_id == ownerInfo.id)
                addInvitingListScreen(invite);
            else
                addInvitedListScreen(invite);
        });
        openSubWindow('#invite_list');
    }
    
    function addInvitedListScreen (invite) {
        var listArea    = $('#invite_list .invited_list');
        var inviteArea  = $('.invite_grid:last').clone();
        var ownerArea   = $('.owner_area', inviteArea);
        var inviteOwner = myMixiFullList['m'+invite.owner_id];
        var memberArea  = $('.invited_member_area', inviteArea);
        var otherMember = 0;
        if (!inviteOwner) return;
        $('.date .target_date', inviteArea).text(invite.target_date);
        $('.user_thumb', ownerArea).attr('src', inviteOwner.thumbnailUrl).attr('alt', inviteOwner.nickname);
        $('.display_name', ownerArea).text(inviteOwner.nickname);
        $('.invite_message', inviteArea).text(invite.message);
        $.each(invite.members, function(i, member){
            var inviteeArea = $('.invitee_grid:last').clone();
            var invitee = (member.member_id == ownerInfo.id) ? ownerInfo : myMixiFullList['m'+member.member_id];
            if (!invitee) return ++otherMember;
            $('.user_thumb', inviteeArea).attr('src', invitee.thumbnailUrl).attr('alt', invitee.nickname);
            $('.display_name', inviteeArea).text(invitee.nickname);
            inviteeArea.appendTo(memberArea).show();
        });
        if (otherMember) {
            $('.other_invitee_count', inviteArea).text(otherMember);
            $('.other_invitee_area', inviteArea).show();
        }
        inviteArea.appendTo(listArea).show();
    }
    
    function addInvitingListScreen (invite) {
        if (invite.owner_id != ownerInfo.id) return;
        var listArea   = $('#invite_list .inviting_list');
        var inviteArea = $('.invite_grid:last').clone();
        var ownerArea  = $('.owner_area', inviteArea);
        $('.date .target_date', inviteArea).text(invite.target_date);
        $('.user_thumb', ownerArea).attr('src', ownerInfo.thumbnailUrl).attr('alt', ownerInfo.nickname);
        $('.display_name', ownerArea).text(ownerInfo.nickname);
        $('.invite_message', inviteArea).text(invite.message);
        $.each(invite.members, function(i, member){
            var inviteeArea = $('.invitee_grid:last').clone();
            var invitee = myMixiFullList['m'+member.member_id];
            if (!invitee) return true;
            $('.user_thumb', inviteeArea).attr('src', invitee.thumbnailUrl).attr('alt', invitee.nickname);
            $('.display_name', inviteeArea).text(invitee.nickname);
            inviteeArea.appendTo('.invited_member_area', inviteArea).show();
        });
        inviteArea.appendTo(listArea).show();
    }
    
    function checkInvite (inviteId) {
        if (!inviteId) return;
        var apiUrl = apiUrlPrefix + 'invite_check.php?invite_id=' + inviteId +
            '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(){ },
            function(){ }
        );
    }
    
    function joinInvite (inviteId) {
        if (!inviteId) return;
        var apiUrl = apiUrlPrefix + 'invite_join.php?invite_id=' + inviteId +
            '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(){ addMessage('OK') },
            function(){ addAlert('NG') }
        );
    }
    
    function rejectInvite (inviteId) {
        if (!inviteId) return;
        var apiUrl = apiUrlPrefix + 'invite_reject.php?invite_id=' + inviteId +
            '&stamp=' + (new Date).getTime();
        extAPI(apiUrl,
            function(){ addMessage('お誘いを削除しました。') },
            function(){ addAlert('お誘いの削除に失敗しました。') }
        );
    }
    
    function extAPI (url, fnSuccess, fnFail) {
        if (!url || !fnSuccess || !fnFail) return;
        //alert($.dump(url));
        var params = {};
        if (isRelease)
            params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED;
        else
            url += '&opensocial_owner_id=' + ownerInfo.id + '&opensocial_viewer_id=' + viewerInfo.id;
        params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
        gadgets.io.makeRequest(url, function(ret){
            if (!ret || !ret.data || ret.data.return_code != 0) {
                //if (ret.data) alert($.dump(ret.data));
                //else if (ret) alert($.dump(ret));
                fnFail();
            }
            else
                fnSuccess(ret.data);
        }, params);
    }
    
    function open_notification (memberId, title, body, callback) {
        if (!memberId) return;
        if (!title) title  = '【ひまシェア】';
        if (!body)  body   = "http://mixi.jp/run_appli.pl?id="+appId;
        var params = {};
        params[opensocial.Message.Field.TITLE] = title;
        var msg = opensocial.newMessage(body, params);
        opensocial.requestSendMessage(memberId, msg, function(response) {
            if (response.hadError()) {
                var code = response.getErrorCode();
                addError('メッセージの送信に失敗しました。');
                return;
            }
            addMessage('メッセージを送信しました。');
            callback();
        });
    }

    function addAlert (message) {
        if (!message) return;
        $('#message_window .alert_area').text(message);
        openSubWindow('#message_window');
    }

    function addNotation (message, onClickFunction) {
        if (!message) return;
        var noteArea = $('.warning_area');
        $('.notation_area', noteArea).text(message)
            .click(function(){ onClickFunction(); });
    }
    
    function addTips (message, x, y) {
        var frm = $('#tips_frame').clone().appendTo('.container');
        //$('.tips_middle', frm).corner();
        $('.tips_area', frm).text(message);
        var windowWidth = $(window).width();
        var offsetX = 10;
        var offsetY = 0;
        var posX;
        var posY = mouseY - frm.height() - offsetY;
        if (mouseX < windowWidth / 2) {
            posX = mouseX + offsetX;
            $('.tips_tail_right', frm).show();
            $('.tips_tail_left' , frm).hide();
        }
        else {
            posX = mouseX - frm.width() - offsetX;
            $('.tips_tail_right', frm).hide();
            $('.tips_tail_left' , frm).show();
        }
        frm.css('top', posY).css('left', posX).fadeIn('1500')
            .oneTime(1000 + message.length * 50, function(){ $(this).fadeOut('1500', function(){ $(this).remove }) })
            .click(function(){ $(this).fadeOut('1500', function(){ $(this).remove }) });
    }
    
    function refreshCalendar () {
        $('.h_date').empty();
        $('.date_title').empty();
        $('.show_mine').empty();
        $('.my_mixi_hima').empty();
        writeCurrentMonth();
        colorWeekEnds();
        makeDateArray();
        getHolidays();
        colorToday();
        var idList = new Array();
        $.each(showingUserIds, function(i,n){idList.push(n.substr(1))});
        if (ownerInfo.id == viewerInfo.id) {
            idList.push(ownerInfo.id);
            idList = $.unique(idList);
        }
        getSchedule(idList);
        getScheduleCount(optinUserIds)
        return;
    }
    
    function writeCurrentMonth () {
        var today = new Date();
        var showDate = new Date(today.getFullYear(), today.getMonth() + monthShift, today.getDate());
        var showDateText = showDate.getFullYear() + '年' + '　' + (showDate.getMonth() + 1) + '月';
        $('.show_month').text(showDateText);
    }
    
    function colorWeekEnds () {
        $('.date_area').each(function(){
            var index = $('.date_area').index(this) % 7;
            if      (index == 0) $(this).css('background-color', bgColorSunday);
            else if (index == 6) $(this).css('background-color', bgColorSaturday);
            else                 $(this).css('background-color', bgColorNormalDay);
        });
    }
    
    function makeDateArray () {
        var today = new Date();
        var baseAdjust  = (new Date(today.getFullYear(), today.getMonth() + monthShift, 0)).getDay();
        var daysInMonth = (new Date(today.getFullYear(), today.getMonth() + monthShift + 1, 0)).getDate();
        for (var i = 0; i < 42; i++) {
            var adjust = i - baseAdjust;
            var recDate = new Date (today.getFullYear(), today.getMonth() + monthShift, adjust);
            var year  = recDate.getFullYear();
            var month = recDate.getMonth() + 1;
            var date  = recDate.getDate();
            $('.h_date').eq(i).append(month + '/' + date);
            $('.date_area').eq(i).css('color', colorMainMonth);
            if (adjust < 1 || adjust > daysInMonth)
                $('.date_area').eq(i).css('color', colorSubMonth);
            if (month < 10) { month = '0' + month; }
            if (date  < 10) {date   = '0' + date;  }
            var date_str = year + '-' + month + '-' + date;
            dateArray[i] = date_str;
        }
        return;
    }
    
    function getHolidays () {
        var holidayUrl = 'http://www.google.com/calendar/feeds/'
            + 'japanese__ja@holiday.calendar.google.com/public/full-noattendees'
            + '?start-min=' + dateArray[0] + '&start-max=' + dateArray[41] + '&alt=json';
        $.getJSON(holidayUrl, function(json){
            if (!json || !json.feed || !json.feed.entry) return;
            $.each(json.feed.entry, function(i, val){
                var date  = val.gd$when[0].startTime;
                var index = dateToIndex(date);
                var title = val.title.$t;
                var calenderDateGrid = $('.calendar_date_grid').eq(index);
                $('.date_title', calenderDateGrid).text(title);
                var dateArea = $('.date_area').eq(index);
                dateArea.css('background-color', bgColorHoliday);
            });
        });
    }
    
    function colorToday () {
        var today = new Date();
        var year  = today.getFullYear();
        var month = today.getMonth()+1;
        var date  = today.getDate();
        if (month < 10) month = '0'+month;
        if (date  < 10) date  = '0'+date;
        var index = dateToIndex(year+'-'+month+'-'+date);
        $('.date_area').eq(index).css('background-color', bgColorToday);
        return;
    }
    
    function makeHtml () {
        for (var dayIndex = 0; dayIndex < 6; dayIndex++) {
            var headTmpl = $('.date_header:first');
            headTmpl.clone(true).insertAfter(headTmpl);
            var dayTmpl = $('div.calendar_date_grid:first').parent();
            dayTmpl.clone(true).insertAfter(dayTmpl);
        }
        for (var weekIndex = 0; weekIndex < 5; weekIndex++) {
            $('div.week_row:first').clone(true).insertAfter('div.week_row:first');
        }
    }
    
    function makeDiaryUrl (idList, targetDate) {
        var month = targetDate.substring(5,  7) - 0;
        var date  = targetDate.substring(8, 10) - 0;
        var title = diaryTitlePrefix + month + '%2F' + date + diaryTitleSuffix;
        var body = //nameList.join(nameSeparator) + diaryBodyTop + 
            month + '%2F' + date + diaryBodyMiddle + diaryBodyBottom + appId;
        var url = 'http://mixi.jp/add_diary.pl?id=' + viewerInfo.id + '&tag_id=200' +
            '&diary_title=' + title + '&diary_body=' + body + '&allow_id_list=' + idList.join(',');
        return url;
    }

})();
